#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "bmp.h"
#include "common.h"
#define BPP 24

int charArray2Int(unsigned char * b, int s, int f) {
    int ret = 0;
    int i;
    int shift = 0;

    for (i = s; i <= f; i++) {
        ret = ret | ((b[i] & 0xff) << shift);
        shift += 8;
    }
    return ret;
}

void intToCharArray(int val, unsigned char * data, int size) {
    int i;
    int shift = 0;

    for (i = 0; i < size; i++) {
        data[i] = (val >> shift) & 0xff;
        shift += 8;
    }
}

unsigned long ** read_BMP(char* file_name,int *h,int *w) {
    unsigned long ** pixels = NULL;
    FILE *  bfile;
    bfile = fopen(file_name, "r");
    if (bfile == NULL) {
        fprintf(stderr, "Unable to open specified file: %s\n", file_name);
        return NULL;
    }

    bmfh * hdr = (bmfh *)malloc(sizeof(bmfh));
//	fprintf(stderr, "sizeof(bmfh) = %d\n", sizeof(bmfh));

    // Read BMP
    int nread;
    nread = fread(hdr, 1, sizeof(bmfh), bfile);
    if (nread != sizeof(bmfh)) {
        fprintf(stderr, "Unable to read hdr\n");
        free(hdr);
        return NULL;
    }

    int size;
    int offset;
    size = charArray2Int(hdr->bfSize, 0, 3);
    offset = charArray2Int(hdr->bfOffBits, 0, 3);
    fprintf(stderr, "Type: %c%c, Size: %d, Offset: %d\n",
            hdr->bfType[0], hdr->bfType[1], size, offset);

    bmih * info = (bmih *)malloc(sizeof(bmih));
    nread = fread(info, 1, sizeof(bmih), bfile);
    if (nread != sizeof(bmih)) {
        fprintf(stderr, "Unable to read info\n");
        free(info);
        return NULL;
    }
    int biSize;
    int height;
    int width;
    int bitsInPixel;
    int biClrUsed;
    biSize = charArray2Int(info->biSize, 0, 3);
    height = charArray2Int(info->biHeight, 0, 3);
    width = charArray2Int(info->biWidth, 0, 3);
    bitsInPixel = charArray2Int(info->biBitCount, 0, 1);
    biClrUsed = charArray2Int(info->biClrUsed, 0, 3);
//	fprintf(stderr,
//	        "Size: %d, Width: %d, Height: %d, BitCount: %d, ClrUsed: %d\n",
//	        biSize, width, height, bitsInPixel, biClrUsed);

    int i, j;
    unsigned long * rgbColorTable = NULL;
    if (bitsInPixel < 24) {
        if (biClrUsed == 0) {
            biClrUsed = 1 << bitsInPixel;
        }
        rgbColorTable = (unsigned long *)malloc(biClrUsed * sizeof(int));
        for (i = 0; i < biClrUsed; i++) {
            nread = fread(&(rgbColorTable[i]), 1, 4, bfile);
            //fprintf(stderr, "rgbcolor[%d] = %8.8lx\n", i, rgbColorTable[i]);
        }
    }

    if (bitsInPixel != 24 && bitsInPixel != 8) {
        fprintf(stderr, "Unsupported bit size of %d\n", bitsInPixel);
        return NULL;
    }

    int bytesPerPixel = bitsInPixel / 8;
    int bytesPerRow = width * bytesPerPixel;
    bytesPerRow = (bytesPerRow & 0x3) ?
                  ((bytesPerRow >> 2) + 1) << 2 : bytesPerRow; 	// bytes per row after pad
    int pad = bytesPerRow - (width * bytesPerPixel);


    pixels = (unsigned long **)malloc(height * sizeof(unsigned long *));
    unsigned char pixelbytes[4];
    for (i = 0; i < height; i++)
        pixels[i] = (unsigned long*) malloc(width * sizeof(unsigned long));
    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            nread = fread(pixelbytes, 1, bytesPerPixel, bfile);
            if (nread != bytesPerPixel) {
                fprintf(stderr, "Unable to read pixel bytes\n");
                free(pixels);
                return NULL;
            }
            if (bytesPerPixel == 3) {
                pixels[height - i -1][j] = (unsigned long)charArray2Int(pixelbytes, 0, 2);
            } else {
                if (pixelbytes[0] >= biClrUsed) {
                    fprintf(stderr, "pixel color index out of range: %d at (%d,%d)\n",
                            pixelbytes[0], i, j);
                    free(pixels);
                    return NULL;
                }
                pixels[height - i - 1][j] = rgbColorTable[pixelbytes[0]];
            }
        }

        // Read the pad bytes that word align the row of pixels

        nread = fread(pixelbytes,1, pad, bfile);
    }
    fclose(bfile);
    *w=width;
    *h=height;
    return pixels;

}

int write_BMP(char* fileName, unsigned long** pix, int height, int width) {
    //int width = inImg.w;
    //int height = inImg.h;
    int i,j;

    int bytesPerPixel = BPP / 8;
	int bytesPerRow = width * bytesPerPixel;
	bytesPerRow = (bytesPerRow & 0x3) ?
	              ((bytesPerRow >> 2) + 1) << 2 : bytesPerRow; 	// bytes per row after pad
	int pad = bytesPerRow - (width * bytesPerPixel);

    FILE *f = fopen(fileName, "w");

	/// header
	bmfh * hdr = (bmfh *)malloc(sizeof(bmfh));

	int size;
	int offset;

	offset = sizeof(bmfh) + sizeof(bmih);
	size = offset + height *bytesPerRow;

	fprintf(stderr, "Type: %c%c, Size: %d, Offset: %d\n",
	        hdr->bfType[0], hdr->bfType[1], size, offset);

    /// set header
    hdr->bfType[0] = 'B';
    hdr->bfType[1] = 'M';
    intToCharArray(size, hdr->bfSize, 4);
    intToCharArray(offset, hdr->bfOffBits, 4);

    fwrite(hdr, 1, sizeof(bmfh), f);

    /// bmp info header
	bmih * info = (bmih *)malloc(sizeof(bmih));

	int biSize = 40;
	int biClrUsed = 0;

	intToCharArray(biSize, info->biSize, 4);
	intToCharArray(width, info->biWidth, 4);
	intToCharArray(height, info->biHeight, 4);
    intToCharArray(BPP, info->biBitCount, 2);
    intToCharArray(biClrUsed, info->biClrUsed, 4);

    fwrite(info, 1, sizeof(bmih), f);



	unsigned char pixelbytes[4];

	for (i = 0; i < height; i++) {
		for (j = 0; j < width; j++) {
			fwrite(&pix[height -1 - i][j], 1, bytesPerPixel, f);
		}
		/// write pad bytes
		memset(pixelbytes, 0, 4);
        fwrite(pixelbytes, 1, pad, f);
	}
        fflush(f);
        fclose(f);

	return 0;
}


int writeGrayBmp(char* fileName, unsigned char** pix, int height, int width) {
    //int width = inImg.w;
    //int height = inImg.h;

    int i,j;

    int bytesPerPixel = BPP / 8;
    int bytesPerRow = width * bytesPerPixel;
    bytesPerRow = (bytesPerRow & 0x3) ?
                  ((bytesPerRow >> 2) + 1) << 2 : bytesPerRow; 	// bytes per row after pad
    int pad = bytesPerRow - (width * bytesPerPixel);

    FILE *f = fopen(fileName, "w");

    /// header
    bmfh * hdr = (bmfh *)calloc(1, sizeof(bmfh));

    int size;
    int offset;

    offset = sizeof(bmfh) + sizeof(bmih);
    size = offset + height *bytesPerRow;


    /// set header
    hdr->bfType[0] = 'B';
    hdr->bfType[1] = 'M';
    intToCharArray(size, hdr->bfSize, 4);
    intToCharArray(offset, hdr->bfOffBits, 4);

    fprintf(stderr, "Type: %c%c, Size: %d, Offset: %d\n",
            hdr->bfType[0], hdr->bfType[1], size, offset);

    fwrite(hdr, 1, sizeof(bmfh), f);

    /// bmp info header
    bmih * info = (bmih *)calloc(1, sizeof(bmih));

    int biSize = 40;
    int biClrUsed = 0;

    intToCharArray(biSize, info->biSize, 4);
    intToCharArray(width, info->biWidth, 4);
    intToCharArray(height, info->biHeight, 4);
    intToCharArray(BPP, info->biBitCount, 2);
    intToCharArray(biClrUsed, info->biClrUsed, 4);

    fwrite(info, 1, sizeof(bmih), f);



    unsigned char pixelbytes[4];

    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            fwrite(&pix[height -1 - i][j], 1, 1, f);
            fwrite(&pix[height -1 - i][j], 1, 1, f);
            fwrite(&pix[height -1 - i][j], 1, 1, f);
        }
        /// write pad bytes
        memset(pixelbytes, 0, 4);
        fwrite(pixelbytes, 1, pad, f);
    }
    fflush(f);
    fclose(f);
    return 0;
}

int writeBinBmp(Image binImg, char* fileName) {
    int i,j;
    int width = binImg.w;
    int height = binImg.h;
    int bytesPerPixel = 3;
    int bytesPerRow = width * bytesPerPixel;
    bytesPerRow = (bytesPerRow & 0x3) ?
                  ((bytesPerRow >> 2) + 1) << 2 : bytesPerRow; 	// bytes per row after pad
    int pad = bytesPerRow - (width * bytesPerPixel);

    FILE *f = fopen(fileName, "w");

    /// header
    bmfh * hdr = (bmfh *)calloc(1, sizeof(bmfh));

    int size;
    int offset;

    offset = sizeof(bmfh) + sizeof(bmih);
    size = offset + height *bytesPerRow;


    /// set header
    hdr->bfType[0] = 'B';
    hdr->bfType[1] = 'M';
    intToCharArray(size, hdr->bfSize, 4);
    intToCharArray(offset, hdr->bfOffBits, 4);

    fwrite(hdr, 1, sizeof(bmfh), f);

    /// bmp info header
    bmih * info = (bmih *)calloc(1, sizeof(bmih));

    int biSize = 40;
    int biClrUsed = 0;

    intToCharArray(biSize, info->biSize, 4);
    intToCharArray(width, info->biWidth, 4);
    intToCharArray(height, info->biHeight, 4);
    intToCharArray(BPP, info->biBitCount, 2);
    intToCharArray(biClrUsed, info->biClrUsed, 4);

    fwrite(info, 1, sizeof(bmih), f);
    unsigned char data;
    unsigned char pixelbytes[4];

    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {

            if(binImg.pixels[height -1 - i][j]) {
                data = 0x00;
            }
            else
                data = 0xff;
            fwrite(&data, 1, 1, f);
            fwrite(&data, 1, 1, f);
            fwrite(&data, 1, 1, f);
        }
        /// write pad bytes
        memset(pixelbytes, 0, 4);
        fwrite(pixelbytes, 1, pad, f);
    }
    fflush(f);
    fclose(f);
    return 0;
}
